package org.dant.ant.extension.tasks;

import java.io.IOException;
import java.util.*;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.util.FileUtils;

import org.dant.ant.extension.types.Host;
import org.dant.ant.extension.types.HostSet;

import org.dant.ant.extension.tasks.internal.Endpoint;
import org.dant.ant.extension.tasks.internal.RemoteCommand;
import org.dant.ant.extension.tasks.internal.RemoteResponse;
import org.dant.ant.extension.tasks.commands.PingCommand;
import org.dant.ant.extension.utils.ParameterUtil;

public abstract class RemoteTask extends Task {

  protected boolean failOnError;
  protected Long timeout;
  
  protected Vector hosts = new Vector();
  protected Vector hostsets = new Vector();

  protected RemoteCommand rcmd;
  protected RemoteResponse response;

  protected boolean transmit = true;
  
  private List cmdSenderList = new ArrayList();
  protected List resultsList = null;

  protected TestCase testcase = null;
  protected Map destHosts = new HashMap();
  protected FileUtils fu = FileUtils.newFileUtils();

  private boolean initializedHost = false;
  
  public RemoteTask() {
    resultsList = new ArrayList();
  }

  public void setTransmit(boolean transmit) {
    this.transmit = transmit;
  }

  public void setOwningTestCase(TestCase testcase) {
    this.testcase = testcase;
  }

  public TestCase getOwningTestCase() {
    if (testcase == null) setOwningTestCase(new TestCase());
    return this.testcase;
  }

  public String getTestSuiteName() {
    return getOwningTestCase().getOwningTestSuite().getName();
  }
  
  public String getTestCaseName() {
    return getOwningTestCase().getName();
  }

  public String getProjectName() {
    return getProject().getName();
  }

  public String getTargetName() {
    return getOwningTarget().getName();
  }

  public boolean getTransmit() {
    return this.transmit;
  }

  public void setHostRef(Reference r) {
    Object o = r.getReferencedObject(getProject());
    if (!(o instanceof Host)) {
      String msg = r.getRefId() + " doesn\'t denote a host";
      throw new BuildException(msg);
    }
    hosts.addElement((Host)o);
  }

  public void setHostSetRef(Reference r) {
    Object o = r.getReferencedObject(getProject());
    if (!(o instanceof HostSet)) {
      String msg = r.getRefId() + " doesn\'t denote a hostset";
      throw new BuildException(msg);
    }
    
    hostsets.addElement((HostSet)o);
  }
  
  public void addHost(Host host) {
    this.hosts.addElement(host);
  }
  
  public void addHostset(HostSet hostset) {
    this.hostsets.addElement(hostset);
  }

  public void setFailonerror(boolean f) {
    this.failOnError = f;
  }
  
  public boolean getFailOnError() {
    return this.failOnError;
  }

  public void setTimeout(Long t) {
    this.timeout = t;
  }

  public Long getTimeOut() {
    return this.timeout;
  }

  private void sendCMD2Host(Endpoint endpoint, RemoteCommand rcommand, boolean transmit) {
    CommandSender cmdSender = new CommandSender();
    cmdSender.setEndpoint(endpoint);
    cmdSender.setTransmit(transmit);
    cmdSender.setRemoteCommand(rcommand);
    cmdSender.start();
    cmdSenderList.add(cmdSender);
  }

  private boolean isServerAlive(Endpoint destHost) {
    PingCommand pc  = new PingCommand();
    pc.setEcho("alive");
    pc.setSourceHost(Endpoint.getLocalhost());
    try {
      pc.init();
      RemoteResponse response = destHost.getRemoteHelper().invoke(destHost, pc);
      if (response.isSuccess() && "alive".equals((String)response.getResult().trim())) {
        return true;
      } else {
        return false;
      }
    } catch (Exception e) {
      return false;
    }
  }

  private Endpoint resolveHost(Host host) {
    Endpoint destHost = new Endpoint(host.getHostName(),
                                     host.getIp(),
                                     host.getPort(),
                                     host.getNetworking(),
                                     host.getBaseDir());
    
    if (destHost.getRemoteHelper() == null) {
      if (failOnError) {
        throw new BuildException("Can't initialize the networking class " + host.getNetworking());
      } else {
        return null;
      }
    }

    destHost.setFullURL(destHost.getHostEndpoint());

    if (!transmit) return destHost;
    
    log("Trying the endpoint: " + destHost.getHostEndpoint() + " ...");
    if (!isServerAlive(destHost)) {
      log("       - endpoint: " + destHost.getHostEndpoint() + " failed!");
      destHost.setFullURL(destHost.getIPEndpoint());
      log("Trying the endpoint: " + destHost.getIPEndpoint() + " ...");
      if (!isServerAlive(destHost)) {
        log("       - endpoint: " + destHost.getIPEndpoint() + " failed!");
        if (failOnError) {
          throw new BuildException("Hostname: " + host.getHostName() + " or IP: " +
                                   host.getIp() + " with port:" + host.getPort() + " is not availalbe");
        } else {
          return null;
        }
      }
    }
    return destHost;
  }

  private void validate() {
    if ((hosts.size()+hostsets.size()) == 0) {
      throw new BuildException("No <host> or <hostset> specified in the the remote task");
    }
  }

  private void initialCommand() throws Exception {
    rcmd.appendParameters(ParameterUtil.resolve(this, false));
    rcmd.setSourceHost(Endpoint.getLocalhost());
    rcmd.init();
  }

  private String failToStart(int totalProcess, int hwm) {
    StringBuffer exp = new StringBuffer();
    exp.append("You want to start ");
    exp.append(totalProcess);
    exp.append(" processes which is over the high water mark ");
    exp.append(hwm);
    exp.append(" you defined in the [hostset], the default value for each [host] is 1");
    return exp.toString();
  }
  
  private void initialHosts() throws Exception {

    if (initializedHost) {
      return;
    } else {
      initializedHost = true;
    }
    
    if (hosts.size() > 0) {
      int totalProcess = 0;
      for (int i = 0; i < hosts.size(); i++) {
        Host host = (Host) hosts.elementAt(i);
        if (this instanceof RemoteExecutable) {
          totalProcess = ((Integer)rcmd.getParameters().get("duplicate")).intValue();
          if (totalProcess > host.getHighWaterMark()) {
            throw new BuildException(failToStart(totalProcess, host.getHighWaterMark()).toString());
          }
        }
        Endpoint ep = resolveHost(host);
        if (ep != null)
          destHosts.put(ep, rcmd);
      }
    }

    if (hostsets.size() > 0) {
      int hwm = getHighWaterMark();
      boolean share = false;
      int totalProcess = 0;

      if (this instanceof RemoteExecutable) {
        share = ((Boolean)rcmd.getParameters().get("share")).booleanValue();
        totalProcess = ((Integer)rcmd.getParameters().get("duplicate")).intValue();
        if (share && hwm < totalProcess) {
          throw new BuildException(failToStart(totalProcess, hwm).toString());
        }
      }
      
      for (int i = 0; i < hostsets.size(); i++) {
        HostSet hostset = (HostSet) hostsets.elementAt(i);
        
        for (Iterator iter=hostset.list().iterator();iter.hasNext();) {
          Host host = (Host) iter.next();
          Endpoint ep = resolveHost(host);
          if (ep == null) continue;

          if (share) {
            if (totalProcess <= 0) return;
            Integer dupli;
            if (host.getHighWaterMark() < totalProcess)
              dupli = new Integer(host.getHighWaterMark());
            else
              dupli = new Integer(totalProcess);
            
            RemoteCommand request = (RemoteCommand) rcmd.clone();
            request.appendParameters("duplicate", dupli);
            destHosts.put(ep, request);
            totalProcess -= dupli.intValue();
          } else {
            destHosts.put(ep, rcmd);
          }
        }
      }
    }
  }

  private int getHighWaterMark() {
    int cumulation = 0;
    if (hostsets.size() >0) {
      for (int i = 0; i < hostsets.size(); i++) {
        cumulation += ((HostSet)hostsets.elementAt(i)).getHighWaterMark();
      }
    }
    return cumulation;
  }

  public void resolveDir() throws IOException {}

  public void initialize() throws Exception {
    initialCommand();
    initialHosts();
    resolveDir();
  }

  public void execute() throws BuildException {
    try {
      initialize();
    } catch (Exception e) {
      e.printStackTrace();
    }

    validate();

    if (destHosts.size() == 0) {
      Host localhost = new Host();
      localhost.setNetworking("org.dant.ant.extension.net.LocalHelper");
      localhost.setHostName("localhost");
      localhost.setIp("127.0.0.1");
      localhost.setPort("1979");
      sendCMD2Host(resolveHost(localhost), rcmd, transmit);
    } else {
      for (Iterator iter=destHosts.keySet().iterator(); iter.hasNext();) {
        Endpoint ep = (Endpoint) iter.next();
        sendCMD2Host(ep, (RemoteCommand)destHosts.get(ep), transmit);
      }
    }

    for (Iterator iter=cmdSenderList.iterator(); iter.hasNext();) {
      CommandSender cmdSender = (CommandSender) iter.next();
      try {
        cmdSender.getThread().join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }

    for (Iterator iter=cmdSenderList.iterator(); iter.hasNext();) {
      CommandSender cmdSender = (CommandSender) iter.next();
      addResult(cmdSender.getResponse());
      handleResults(cmdSender.getResponse());
    }
  }

  protected void handleResults(RemoteResponse response) {
    if (response != null) {
      log(response.toString());
    } else {
      if (failOnError) {
        throw new BuildException("No response returned!");
      }
    }
      
    if (failOnError) {
      if (!response.isSuccess())
        throw new BuildException("Detected fail in this build : " + response.getResult());
    }
  }

  public List getResultsList() {
    return this.resultsList;
  }
  
  public void addResult(RemoteResponse response) {
    this.resultsList.add(response);
  }
}
